﻿#define _CRT_SECURE_NO_WARNINGS
//文档操作

#include <stdio.h>

//打开一个文档                //文档分为二进制文档 与 文本文档
//int main()
//{
//	int a = 66666;
//	FILE* pf = fopen("test.txt", "wb");//wb - write binary 写二进制
//	fwrite(&a, 4, 1, pf);//以二进制的形式写入文档     //注意 依旧小端字节序
//	fclose(pf);//关闭文档
//	pf = NULL;
//	return 0;
//}

//打开路径 
//int main()
//{
//	//FILE* fp = fopen("D:/mygitee/date.txt", "w");  //绝对路径 从硬盘开始写起
//	//FILE* fp = fopen("D:\\mygitee\\date.txt", "w");  //使用这种斜杠表示地址时 注意转义字符的干扰 最好用两次斜杠
//	FILE* fp = fopen("./../../date.txt", "w");  //相对路径 一个点. 表示当前路径 两个点.. 表示上一级路径  读路径的时候也是从左往右读 
//	//FILE* fp = fopen("../../../date.txt", "w");   //上三级路径
//	if (fp == NULL)
//	{
//		perror("fopen");
//		return 1;
//	}
//
//	fputc('C', fp); 
//	fclose(fp);
//	fp = NULL;
//
//	return 0;
//}

//test_fputc 
//int main()
//{
//	FILE* fp = fopen("date.txt", "w");  //像这种直接写一个文件不写路径 就是默认当前路径
//	if (fp == NULL)
//	{
//		perror("fopen");
//		return 1;
//	}
//	int i = 0;
//	for (i = 65; i < 98; i++)
//	{
//		fputc(i, fp);  //使用fputc 字符输出函数 //注意 这些文件函数是输入还是输出是对于我们这个IDE 而言的 如果对于文件来说 那就是输入嘛
//	}
//	fclose(fp);
//	fp = NULL;
//
//	return 0;
//}

//test_fgetc 
//int main()
//{
//	FILE* fp = fopen("date.txt", "r");  
//	if (fp == NULL)
//	{
//		perror("fopen");
//		return 1;
//	}
//
//	//使用for 循环读取  在我们不清楚这个文件的字符数量的时候 我们可能没办法精确的将该文件中的所有字符都读取出来 
//	int i = 0;
//	for (i = 65; i < 100; i++) 
//	{
//		char ch = fgetc(fp);  //字符输入函数  //对于文件而言就是字符输出函数
//		printf("%c ", ch);  //注意 fgetc 函数从文件中读取字符后 是有返回值的 需要先接收返回值 再打印 才能打印出来 fgetc 本身并不会直接打印出来
//	    //且每次打开一个文件的时候光标都是位于文件起始位置的 而每读取一个fgetc 一个字符 这个光标就会往后一个字符
//	}
//
//	////使用while 循环 更有利于将所有字符都读取出来
//	////char ch = 0;
//	//int ch = 0;  //字符存入内存中时 本来就是通过ASCII码值 进行存储的
//	//while ((ch = fgetc(fp)) != EOF)    
//	//{
//	//	printf("%c ", ch);
//	//	//putchar(ch); //putchar 函数只能打印一个字符 因此无法做到格式控制 而printf 函数可以做到格式控制
//     //  putchar(' '); //putchar 做到格式控制的方式 
//	//}
//
//
//	fclose(fp);
//	fp = NULL;
//
//	return 0;
//}

//fgetc 直接从标准输入流中读取数据
//int main()
//{
//	int i = 0;
//	for (i = 0; i < 10; i++)
//	{
//		int ch = fgetc(stdin); //直接从标准输入流stdin 中读取数据 会读取空格 可以一次性输入完 然后可以10个字符含空格  //也可以输入一个按一次回车 然后会打印一个 但这样那个回车\n 也会被读取到  因此实际有效读取就变成5个了 
//		printf("%c", ch);
//	}
//	
//	return 0;
//}

//stdin stdout 标准输入输出流默认都是开启的  
//此处 实际使用
//int main()
//{
//	//test1
//	//int ch = fgetc(stdin);
//	//fputc(ch, stdout); //只能打印出来一个  
//	////fputs(ch, stdout); //err //此处第一个参数需要的是地址 因此不能使用变量ch
//
//	//test2
//	char ch[99] = { 0 };
//	int i = 0;
//	for (i = 0; i < 99; i++)
//	{
//		ch[i] = fgetc(stdin);
//	}
//	fputs(ch, stdout);
//
//	return 0;
//}

//使用fputs 函数
//int main()
//{
//	FILE* pf = fopen("data.txt", "w");
//	if (pf == NULL)
//	{
//		perror("fopen");
//		return 1;
//	}
//	fputs("aaaaaaa", pf);  //将文件data.txt 的内容更改了  
//	fputs("bbbbbb\n", pf);   //直接追加到aaaaaaa 的后面 
//	fputs("ccccccc\n", pf);  //另起一行 写入
//
//	fclose(pf);
//	pf = NULL;
//
//	return 0;
//}

//使用fgets 函数
//int main()
//{
//	FILE* pf = fopen("data.txt", "r"); //注意读取的时候 要是"r"
//	if (pf == NULL)
//	{
//		perror("fopen");
//		return 1;
//	}
//	//char str[100] = { 0 };
//	//fgets(str, 10, pf); //实际有效读取为9 最后一个自动存入\0 
//	//printf(str);
//
//	//读取多行时
//	//char arr[20] = { 0 };
//	//fgets(arr, 10, pf); //读取只会读取一行的字符 但受限于最大读取数量 也就是第二个参数 而具体会不会跳行 本行未读完时不会换行 但具体还是要看光标的位置 
//	//fgets(arr, 10, pf); //此处 读取的都是同一行 并且存入arr 数组时 每次fgets 读取都是从arr 数组的起始位置 开始存入的 因此 此处会造成第一个fgets 的部分数据被覆盖
//
//	//多行读取 并打印每行
//	char arr2[20] = { 0 };  //要保证每行的字符都能被读取到 此处20足矣
//	fgets(arr2, 20, pf);
//	printf(arr2); 
//	fgets(arr2, 20, pf);
//	printf(arr2);
//
//	fclose(pf);
//	pf = NULL;
//
//	return 0;
//}

//fgets fputs 与标准输入 输出流
//int main()
//{
//	char arr[20] = { 0 };
//	fgets(arr, 20, stdin);   //注意分辨输入输出 并对应好
//	fputs(arr, stdout);
//
//	return 0;
//}

//fprintf 的使用
//int main()
//{
//	char name[20] = "zhangsan";
//	int age = 19;
//	double c = 88.88;
//
//	FILE* pf = fopen("date.txt", "w");
//	if (pf == NULL)
//	{
//		perror("fopen");
//		return 1;
//	}
//	fprintf(pf, "%d %.2lf %s\n", age, c, name);  //不定参数的函数 此处也就是比printf 前面多了一个文件指针而已 //且 此处写入浮点数等都是可以控制其写入的格式的 比如说 此处只写入小数点后两位小数
//
//	fclose(pf);
//	pf = NULL;
//
//	return 0;
//}

//fscanf  的使用
//int main()
//{
//	char name[20] = { 0 };
//	int age = 0;
//	double c = 0.0; //0.0f 才是浮点型
//
//	FILE* pf = fopen("date.txt", "r");
//	if (pf == NULL)
//	{
//		perror("fopen");
//		return 1;
//	}
//	fscanf(pf, "%d %lf %s", &age, &c, name); //从文件中读取数据  //注意 此处不能使用%.2lf 读取浮点型数据  因为同学，在scanf中，格式控制符主要用于确定数据类型和读取规则，而不是用于指定输出格式。%.2f主要用在printf输出控制留两位小数的，在scanf中是不合法的格式控制符。
//	printf("%d %lf %s", age, c, name);
//
//	fclose(pf);
//	pf = NULL;
//
//	return 0;
//}

//fscanf 与 fprintf 只要有正确的形式来接收什么类型的都是可以的 比如此处使用结构体类型
//typedef struct Stu
//{
//	char name[20];
//	int age;
//	double c;
//}Stu, * pStu;
//int main()
//{
//	Stu s = { 0 };
//	pStu ps = &s;
//	FILE* pf = fopen("date.txt", "r");
//	if (pf == NULL)
//	{
//		perror("fopen");
//		return 1;
//	}
//	fscanf(pf, "%d %lf %s",&(ps->age) ,&(ps->c) ,ps->name ); //注意 此处要取到地址 当我们选取完结构体成员变量后
//	printf("%d %.2lf %s", ps->age, ps->c, ps->name);  
//
//	fclose(pf);
//	pf = NULL;
//
//	return 0;
//}

//fwrite 是使用
//int main()
//{
//	FILE* pf = fopen("test.txt", "wb");
//	if (pf == NULL)
//	{
//		perror("fopen");
//		return 1;
//	}
//	int arr[20] = { 1,2,3,4,5,6,7,8,9,10 };
//	fwrite(arr, sizeof(arr[0]), 20, pf);
//
//	fclose(pf);
//	pf = NULL;
//
//	return 0;
//}

//fread 的使用
//int main()
//{
//	FILE* fp = fopen("test.txt", "rb");
//	if (fp == NULL)
//	{
//		perror("fopen");
//		return 1;
//	}
//	int arr[20] = { 0 };
//	fread(arr, sizeof(arr[0]), 20, fp);
//	//所有的读取函数 都是将读取到的数据存储到相应的变量中 而并不是直接打印 除非是直接与标准输出流stdout 连用
//
//	fclose(fp);
//	fp = NULL;
//
//	return 0;
//}

//sprintf 与sscanf 函数的使用 
//sprintf 作用就是将数据转换成字符的形式
//sscanf 的作用就是将被转换的字符都转换成原先的数据
// 注意sprintf 与sscanf 函数都不是文件函数
//int main()
//{
//	char name[20] = "jiazhima";
//	int age = 22;
//	double c = 39.0;
//
//	char arr[100] = { 0 };
//	sprintf(arr, "%s %d %lf", name, age, c);
//
//	char name2[20] = { 0 };  //将前面的name 数组置0 比较麻烦 没有再新建一个方便
//	int age2 = 0;
//	double c2 = 0.0;
//
//	sscanf(arr, "%s %d %lf", name2, &age2, &c2); 
//	printf("%s %d %lf", name2, age2, c2);
//
//	return 0;
//}

//fseek ftell rewind 函数的使用
//int main()
//{
//	//写入数据
//	FILE* fp = fopen("test2.txt", "w");  
//	if (fp == NULL)
//	{
//		perror("fopen");
//		return 1;
//	}
//	fputs("abcdefgh", fp);
//	fclose(fp);
//	fp = NULL;
//
//	//读取数据 与光标的移动
//	FILE* fp2 = fopen("test2.txt", "r"); //以读取的方式打开文件  //刚打开时光标在起始位置
//	if (fp2 == NULL)
//	{
//		perror("fopen"); 
//		return 1;
//	}
//	int ch = fgetc(fp2);  //每fgetc 读取一次 光标往后走一位
//	printf("%c ", ch);
//
//	ch = fgetc(fp2);
//	printf("%c ", ch);
//
//	//fseek(fp2, 4, SEEK_SET); //SEEK_SET 源头位置就是文件的起始位置 4 表示的就是距离起始位置的偏移量 正数表示向后偏移 负数则相反
//	//fseek(fp2, 2, 2); //此处的打印结果与上一行代码相同吗？ // err 且不相同  //注意 源头origin 参数 是固定的三个分别是SEEK_SET,   SEEK_CUR,    SEEK_END 分别代表了文件的起始位置 光标的当前位置 文件的末尾位置 而不能是一个常数
//	//fseek(fp2, 2, SEEK_CUR);
//	fseek(fp2, -4, SEEK_END);//另外 我们也可以通过fseek 函数的使用 通过它的第三个参数的 来刷新 并重新找到这个光标的位置
//
//	ch = fgetc(fp2);
//	printf("%c ", ch);
//
//	printf("%ld\n", ftell(fp2)); //当我们不知道光标走到哪里的时候 可以通过使用ftell 函数来告诉我们光标所在的位置
//	//此处ftell(fp2) 通过printf 打印出来的光标位置 是该光标所在位置距文件起始位置的偏移量
//	//注意 此处是使用%ld  的输出格式来打印的 因为在ftell 函数定义的时候 其函数类型就是long int 
//
//	ch = fgetc(fp2);
//	printf("%c ", ch);
//
//	rewind(fp2);   //rewind 函数就是将光标从新移动到文件的起始位置  wind 就是发条的意思 rewind 再上发条 也就是重新刷新嘛
//
//	ch = fgetc(fp2); //a
//	printf("%c ", ch);
//
//	fclose(fp2);
//	fp2 = NULL;
//
//	return 0;
//}

//证明缓冲区的存在
//#include <windows.h>
////VS2022 WIN11环境测试
//int main()
//{
//	FILE* pf = fopen("test3.txt", "w");
//	fputs("abcdef", pf);//先将代码放在输出缓冲区
//	printf("睡眠20秒-已经写数据了，打开test.txt文件，发现文件没有内容\n");
//	Sleep(20000); //Sleep 函数的睡眠的时间单位就是毫秒ms 而1s = 1000ms 
//	printf("刷新缓冲区\n");
//	fflush(pf);//刷新缓冲区时，才将输出缓冲区的数据写到文件（磁盘）
//	//注：fflush 在⾼版本的VS上不能使⽤了
//	printf("再睡眠10秒-此时，再次打开test.txt文件，文件有内容了\n");
//	Sleep(10000);
//	fclose(pf);
//	//注：fclose在关闭文件的时候，也会刷新缓冲区  //为此这就是设计第二个Sleep 的原因
//	pf = NULL;
//	return 0;
//}

//文件读取结束的判断

//文本文件
//ferror feof 函数的使用
//#include <stdlib.h>
//int main(void)
//{
//	int ch; // 注意：int，⾮char，要求处理EOF  //因为EOF 是-1 而负数是不存在字符中的  因此只能使用整型类型来处理
//	FILE* fp = fopen("test3.txt", "r");
//	if (!fp)  //优化写法!fp 即fp 只有为NULL 0时 该判断才成立 
//	{
//		perror("fopen");
//        return EXIT_FAILURE; //EXIT_FAILURE 就是错误返回的意思
//		//return 1; 等价
//	}
//	//注意 fgetc 当读取失败的时候或者遇到⽂件结束的时候，都会返回EOF   //而如果是使用fgets 则在读取失败的时候 会返回NULL 空指针那么我们去判断这个空指针即可
//	while ((ch = fgetc(fp)) != EOF) // 标准C I/O读取⽂件循环 
//	{
//		putchar(ch);  //此处while 循环读取 主要是为了遍历读取文件 光标遍历一遍
//	}
//	//判断是什么原因结束的、
//	//结束的两个原因 1. 读取到文件末尾  2. 读取过程中发生读取错误
// //注意这个读取过程中 机器会自动改变标识 而当我们使用ferror feof 函数的时候 它们会自动去看它们所对应的标识 是否为真
//	if (ferror(fp)) //ferror 函数 来判断是否是因为读取出错而返回的 ferror 函数在运行的时候 会直接去看有无错误标识 若错误标识为1 则有错误标识 那么ferror 则运行 
//		puts("\nI/O error when reading"); //直接使用puts 就是直接打印字符串 与直接使用gets 输入字符串一样 //两者都不是文件函数
//	else if (feof(fp)) //在文本文件中 两个用来判断文本读取结束原因的函数 feof 就是用来判断是否是因为读取到文件结尾才读取结束的 feof 与ferror 一样都会去找他们对应的标识 是否有所变化 是1 还是0 若是1 则判断为真
//		puts("\nEnd of file reached successfully");
//
//	fclose(fp);
//	fp = NULL;
//
//	return 0;
//}

//二进制文件
//读取结束的判断
//enum { SIZE = 10 }; //此处 使用枚举类型enum 来定义枚举常量SIZE 并将其初始化为5  就是为了不使用define 来定义 从而使得SIZE 的使用更为便利 
//此处的枚举常量 实际存入的值就是5  
//尽管后面并没有定义枚举变量 但该枚举常量已经可以替代5使用了 且有自身的枚举类型 并且还能调试
//可理解为优于define 去定义的情况

//int main(void) 
//{
//	//写入二进制数据
//	double arr[SIZE] = { 1.,2.,3.,4.,5. ,6.,7.,8.,9.,10.};  //此处使用1. 2. 3. 4. 5. 有小数点 就省略了后面的0 其实也就相当于1.0 2.0 3.0 4.0 5.0 也就是double 类型
//	FILE* fp = fopen("test4.bin", "wb");  //.bin 就代表是二进制文件 而.txt就是代表文本文件
//	if(fp == NULL)
//		{
//			perror("fopen"); 
//			return 1;
//		}
//    fwrite(arr, sizeof*arr, SIZE, fp); //此处sizeof*arr 就是直接sizeof 了arr 数组首个元素  arr 本身就指向arr数组的首元素位置 那么解引用之后 得到的就是首元素
//    fclose(fp);
//	fp = NULL;
//
//	//读取二进制数据
//    double arr2[SIZE];
//    fp = fopen("test4.bin", "rb"); //由于fp 已经指向0地址 为空指针NULL 因此此处可以再次使用文件指针fp
//	if (fp == NULL)
//	{
//		perror("fopen2");
//		return 2;
//	}
//    size_t ret_code = fread(arr2, sizeof(arr2[0]), SIZE, fp); //注意 fread 函数的返回值 与scanf 相同 都是返回成功读取的元素个数 //ret_code 中ret 就是return 的意思 就是返回的代码 当然也可以是result 结果的意思 那么ret_code 也就是结果代码的意思
//    //重点
//	if (ret_code == SIZE) //如果读取到的数量与实际我们所期望的数量相同 那么就是成功读取  因为不然的话就是读的比我们所期望的少了 那么要么就是它一共就那么多 没有我们期望的多 要么就是在读取的时候发生了读取错误
//	{
//	    puts("Array read successfully, contents");
//		for (int n = 0; n < SIZE; ++n)
//		{
//			printf("%f ", arr2[n]);
//		}
//	    putchar('\n'); //\n 是一个转义字符 也是字符 也可以使用putchar 打印 
//    }
//	else  //当读取到的个数与我们所期望的个数不同时
//	{ 
//		if (feof(fp))
//			printf("Error reading test.bin: unexpected end of file\n");
//		else if (ferror(fp))
//		{
//			perror("Error reading test.bin");
//		}
//	}
//
//    fclose(fp);
//	fp = NULL;
//	return 0;
//}


//使用标准输入流stdin 直接将数据写入文件中 再将写入的数据全都读取出来 
int main()
{
	FILE* fp = fopen("test5.txt", "w");
	if (!fp)
	{
		perror("fopen");
		return 1;
	}
	int arr[100] = { 0 };
	fgets(arr, 100, stdin);  //直接将标准输入流的数据 
	fputs(arr, fp); //存储进去

	printf("%s\n", arr); //可直接打印 不需要再fopen 再"r" 

	fclose(fp);
	fp = NULL;
	return 0;
}